package com.spukmk3me.impl.libgdx;

import java.io.IOException;
import java.util.List;

import com.spukmk3me.Platform;
/* $if SPUKMK3ME_DEBUG$ */
import com.spukmk3me.debug.Logger;
/* $endif$ */
import com.spukmk3me.math.FPMath;
import com.spukmk3me.math.Matrix4f;
import com.spukmk3me.math.Rectangle;
import com.spukmk3me.math.Vector3f;
import com.spukmk3me.resource.Resource;
import com.spukmk3me.resource.ResourceManager;
import com.spukmk3me.scene.Entity;
import com.spukmk3me.video.Image;
import com.spukmk3me.video.ImageRegion;
import com.spukmk3me.video.Polygon;
import com.spukmk3me.video.Sprite;
import com.spukmk3me.video.VideoAPI;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.GL11;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;

public final class LibgdxVideoAPI extends VideoAPI
{
    public LibgdxVideoAPI()
    {
        m_initialized = false;
    }
    
    @Override
    public void init()
    {
        if ( m_initialized )
            return;
        
        /* $if SPUKMK3ME_DEBUG$ */
        Logger.log( "Initializing Libgdx video API..." );
        /* $endif$ */

        m_initialized = true;
        
        // Image rendering initialization
        m_cam = new OrthographicCamera();
        resize( Gdx.graphics.getWidth(), Gdx.graphics.getHeight() );
        
        m_clipData = new Rectangle();
        
        m_additionalColor       = new float[ 4 ];
        m_renderBuffer          = new RenderBuffer();
        m_mvMatrixStack         = new Matrix4f[ 16 ];
        m_mvScaleStack          = new Vector3f[ 16 ];
        m_mvTransStack          = new Vector3f[ 16 ];
        m_mvNearestGLMatrix     = new int[ 16 ];
        m_mvNeedUpdateGLMatrix  = new boolean[ 16 ];
        m_mvMatrixStackIndex    = -1;
        
        for ( int i = 0; i != m_mvMatrixStack.length; ++i )
        {
            m_mvMatrixStack[ i ] = new Matrix4f();
            m_mvScaleStack[ i ] = new Vector3f();
            m_mvTransStack[ i ] = new Vector3f();
        }
        
        int[] _glStackSize = new int[ 1 ];
        Gdx.gl10.glGetIntegerv( GL10.GL_MAX_MODELVIEW_STACK_DEPTH, _glStackSize, 0 );
        m_mvMatrixGLStackSize   = _glStackSize[ 0 ];
        
        ///////////////////////////////////////////
        m_useBlending = false;
        markGLCreateNewContext();
        m_renderBuffer.init();
        
        /* $if SPUKMK3ME_DEBUG$ */
        Logger.log( "done.\n" );
        /* $endif$ */
    }
    
    @Override
    public void cleanup()
    {
        m_renderBuffer.dispose();
    }
    
    @Override
    public void startRendering( boolean clearScreen, int clearColor, long deltaMilliseconds )
    {
        Gdx.gl10.glDepthMask( false );
        Gdx.gl10.glEnable( GL10.GL_TEXTURE_2D );
        
        Gdx.gl10.glEnableClientState( GL10.GL_VERTEX_ARRAY );
        Gdx.gl10.glEnableClientState( GL10.GL_COLOR_ARRAY );
        Gdx.gl10.glEnableClientState( GL10.GL_TEXTURE_COORD_ARRAY );
        
        if ( m_glCreateNewContext )
        {
            Pixmap whitePixmap = new Pixmap( 1, 1, Format.RGBA4444 );
            whitePixmap.setColor( Color.WHITE );
            whitePixmap.fillRectangle( 0, 0, 1, 1 );
            m_whiteTexture = new Texture( whitePixmap );
            
            m_renderBuffer.glContextCreated();
            reloadTextures();
            
            m_glCreateNewContext = false;
        }
        
        if ( m_useBlending )
        {
            Gdx.gl10.glEnable( GL10.GL_BLEND );
            Gdx.gl10.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA );
        }
        else
            Gdx.gl10.glDisable( GL10.GL_BLEND );
        
        /* $if SPUKMK3ME_DEBUG$ */
        m_renderBuffer.resetFlushCounter();
        /* $endif$ */
        
        m_deltaTimeL = deltaMilliseconds;
        m_deltaTimeI = FPMath.fpDiv( (int)m_deltaTimeL << 16, 0x03E80000 );
        
        if ( clearScreen )
        {
            Gdx.gl10.glClearColor(
                ((clearColor >> 16) & 0x00FF) / 255f,
                ((clearColor >> 8) & 0x00FF) / 255f,
                (clearColor & 0x00FF) / 255f,
                ((clearColor >> 24) & 0x00FF) / 255f );
            Gdx.gl10.glClear( GL10.GL_COLOR_BUFFER_BIT );
        }
        
        Gdx.gl10.glMatrixMode( GL10.GL_PROJECTION );
        Gdx.gl10.glLoadMatrixf( m_cam.combined.val, 0 );
        Gdx.gl10.glMatrixMode( GL10.GL_MODELVIEW );
        Gdx.gl10.glLoadIdentity();
        m_mvMatrixStackIndex = 0;
        m_mvNearestGLMatrix[ 0 ] = 0;
        m_mvMatrixStack[ 0 ].reset();
        m_mvNeedUpdateGLMatrix[ 0 ] = false;
        m_mvScaleStack[ 0 ].set( 1f, 1f, 1f );
        m_mvTransStack[ 0 ].set( 0f, 0f, 0f );
    }
    
    public void finishRendering()
    {
        m_renderBuffer.flush();
    }

    public Object getProperty( String propertyName )
    {
        if ( propertyName.equals( PROPERTY_NAME ) )
            return LIBGDX_VIDEOAPI_NAME;
        
        if ( propertyName.equals( PROPERTY_VERSION ) )
            return LIBGDX_VIDEOAPI_VER;
        
        return null;
    }
    
    @Override
    public int getScreenHeight()
    {
        return Gdx.graphics.getHeight();
    }

    @Override
    public int getScreenWidth()
    {
        return Gdx.graphics.getWidth();
    }
    
    @Override
    public void setClip( int x, int y, int w, int h )
    {
        m_renderBuffer.flush();
        
        //System.out.println( "Clip input: " + x + ' ' + y + ' ' + w + ' ' + h );
        m_clipData.x = Math.min( getScreenWidth() - 1, Math.max( 0, x ) );
        m_clipData.y = Math.min( getScreenHeight() - 1, Math.max( 0, y ) );
        m_clipData.w = Math.min( getScreenWidth() - m_clipData.x, Math.max( 0, w ) );
        m_clipData.h = Math.min( getScreenHeight() - m_clipData.y, Math.max( 0, h ) );
        //System.out.println( "Clip: " + m_clipData.x + ' ' + m_clipData.y + ' ' + m_clipData.w + ' ' + m_clipData.h );
        
        if (    (x == 0) && (y == 0) &&
                (w == getScreenWidth()) && (h == getScreenHeight()) )
            Gdx.gl.glDisable( GL10.GL_SCISSOR_TEST );
        else
        {
            Gdx.gl.glEnable( GL10.GL_SCISSOR_TEST );
            Gdx.gl.glScissor( x, y, w, h );
        }
    }
    
    @Override
    public long getClip()
    {
        return  ((m_clipData.x & 0x0000FFFF) << 48) |
                ((m_clipData.y & 0x0000FFFF) << 32) |
                ((m_clipData.w & 0x0000FFFF) << 16) |
                (m_clipData.h & 0x0000FFFF);
    }
    
    @Override
    public void getClip( Rectangle rect )
    {
        rect.copy( m_clipData );
    }
    
    @Override
    public void setPlaneColor( int color )
    {
        m_planeColor = Color.toFloatBits( (color >> 16) & 0x00FF,
            (color >> 8) & 0x00FF, color & 0x00FF, (color >> 24) & 0x00FF );
    }
    
    @Override
    public void setAdditionalColor( int color )
    {
        boolean useAdditionalColor = color != 0;
        
        if ( useAdditionalColor != m_useAdditionalColor )
        {
            if ( useAdditionalColor )
            {
                m_renderBuffer.flush();
                
                m_additionalColor[ 0 ] = (float)((color >> 16) & 0x00FF) / 255f;
                m_additionalColor[ 1 ] = (float)((color >> 8) & 0x00FF) / 255f;
                m_additionalColor[ 2 ] = (float)(color & 0x00FF) / 255f;
                m_additionalColor[ 3 ] = (float)((color >> 24) & 0x00FF) / 255f;
                
                Gdx.gl10.glTexEnvf( GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL11.GL_COMBINE );
                Gdx.gl10.glTexEnvf( GL10.GL_TEXTURE_ENV, GL11.GL_COMBINE_RGB, GL11.GL_ADD );
                Gdx.gl10.glTexEnvf( GL10.GL_TEXTURE_ENV, GL11.GL_COMBINE_ALPHA, GL11.GL_ADD );
                Gdx.gl10.glTexEnvf( GL10.GL_TEXTURE_ENV, GL11.GL_SRC0_RGB, GL11.GL_TEXTURE );
                Gdx.gl10.glTexEnvf( GL10.GL_TEXTURE_ENV, GL11.GL_SRC1_RGB, GL11.GL_CONSTANT );
                Gdx.gl10.glTexEnvf( GL10.GL_TEXTURE_ENV, GL11.GL_SRC0_ALPHA, GL11.GL_TEXTURE );
                Gdx.gl10.glTexEnvf( GL10.GL_TEXTURE_ENV, GL11.GL_SRC1_ALPHA, GL11.GL_CONSTANT );
                Gdx.gl10.glTexEnvfv( GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, m_additionalColor, 0 );
            }
            else
            {
                m_renderBuffer.flush();
                Gdx.gl10.glTexEnvf( GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE );
                m_additionalColor[ 0 ] = m_additionalColor[ 1 ] = m_additionalColor[ 2 ] = m_additionalColor[ 3 ] = 0;
                Gdx.gl10.glTexEnvfv( GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, m_additionalColor, 0 );
            }
        }
        
        m_useAdditionalColor = useAdditionalColor;
    }
    
    @Override
    public void setBlending( boolean useBlending )
    {
        if ( m_useBlending == useBlending )
            return;
        
        m_useBlending = useBlending;
        m_renderBuffer.flush();
        
        if ( useBlending )
        {
            Gdx.gl10.glEnable( GL10.GL_BLEND );
            Gdx.gl10.glBlendFunc( GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA );
        }
        else
            Gdx.gl10.glDisable( GL10.GL_BLEND );
    }
    
    @Override
    public void getCurrentMatrix( Matrix4f m )
    {
        m.copy( m_mvMatrixStack[ m_mvMatrixStackIndex ] );
    }

    @Override
    public void mulMatrix( Matrix4f m )
    {
        ++m_mvMatrixStackIndex;
        
        Matrix4f t = m_mvMatrixStack[ m_mvMatrixStackIndex ];
        
        t.copy( m );
        t.mul( m_mvMatrixStack[ m_mvMatrixStackIndex - 1 ] );
        
        //m_renderBuffer.flush();
        //Gdx.gl10.glMatrixMode( GL10.GL_MODELVIEW );
        //Gdx.gl10.glLoadMatrixf( t.getRawData(), 0 );
        m_renderBuffer._setModelViewMatrix( t );
                
        /*if ( m.hasRotation() )
        {
            m_renderBuffer.flush();
            Gdx.gl10.glMatrixMode( GL10.GL_MODELVIEW );
            Gdx.gl10.glLoadMatrixf( t.getRawData(), 0 );
            m_mvNearestGLMatrix[ m_mvMatrixStackIndex ] = m_mvMatrixStackIndex;
            
            m_mvScaleStack[ m_mvMatrixStackIndex ].set( 1f, 1f, 1f );
            m_mvTransStack[ m_mvMatrixStackIndex ].set( 0f, 0f, 0f );
            m_renderBuffer.setSubScaleAndTrans( 1f, 1f, 0f, 0f );
        }
        else
        {
            m_mvNearestGLMatrix[ m_mvMatrixStackIndex ] =
                m_mvNearestGLMatrix[ m_mvMatrixStackIndex - 1 ];
            
            Vector3f vs = m_mvScaleStack[ m_mvMatrixStackIndex ];
            Vector3f vt = m_mvTransStack[ m_mvMatrixStackIndex ];
            float[] d = m.getRawData();
            
            vt.set( m_mvTransStack[ m_mvMatrixStackIndex - 1 ] );
            vs.set( m_mvScaleStack[ m_mvMatrixStackIndex - 1 ] );

            vt.x += d[ 12 ] * vs.x;
            vt.y += d[ 13 ] * vs.y;
            vs.x *= d[ 0 ];
            vs.y *= d[ 5 ];
            
            m_renderBuffer.setSubScaleAndTrans( vs.x, vs.y, vt.x, vt.y );
        }
        
        m_mvNeedUpdateGLMatrix[ m_mvMatrixStackIndex ] = m.hasRotation();*/
    }

    @Override
    public void rollbackMatrix()
    {
        --m_mvMatrixStackIndex;
        
        m_renderBuffer._setModelViewMatrix( m_mvMatrixStack[ m_mvMatrixStackIndex ] );
        
        /*if ( m_mvNeedUpdateGLMatrix[ m_mvMatrixStackIndex + 1 ] )
        {
            m_renderBuffer.flush();
            Gdx.gl10.glMatrixMode( GL10.GL_MODELVIEW );
            Gdx.gl10.glLoadMatrixf(
                m_mvMatrixStack[ m_mvNearestGLMatrix[ m_mvMatrixStackIndex ] ].getRawData(), 0 );
        }
        
        Vector3f vs = m_mvScaleStack[ m_mvMatrixStackIndex ];
        Vector3f vt = m_mvTransStack[ m_mvMatrixStackIndex ];
        
        m_renderBuffer.setSubScaleAndTrans( vs.x, vs.y, vt.x, vt.y );*/
    }
    
    @Override
    public void render( Entity entity, boolean animating )
    {
        switch ( entity.getType() )
        {
            case Entity.ET_IMAGE:
                {
                    LibgdxImageRegion imgRegion = (LibgdxImageRegion)entity;
                    
                    m_renderBuffer._drawImg( ((LibgdxImage)imgRegion.img),
                        imgRegion.w, imgRegion.h,
                        imgRegion.u1, imgRegion.v1, imgRegion.u2, imgRegion.v2, m_planeColor );
                }
                
                break;
                
            case Entity.ET_SPRITE:
                {
                    LibgdxSprite sprite = (LibgdxSprite)entity;
                    
                    int framePosition = sprite.getFramePosition( sprite.getFrameIndex() );
                    
                    if ( framePosition != -1 )
                    {
                        float u = sprite.u + (framePosition % sprite.nW) * sprite.stepU;
                        float v = sprite.v + (framePosition / sprite.nW) * sprite.stepV;
                        
                        m_renderBuffer._drawImg( ((LibgdxImage)sprite.image),
                            sprite.spriteW, sprite.spriteH,
                            u, v + sprite.stepV, u + sprite.stepU, v, m_planeColor );
                    }
                    
                    if ( animating )
                        sprite.update( m_deltaTimeI );
                }
                
                break;
                
            case Entity.ET_POLYGON:
                {
                    Polygon poly = (Polygon)entity;
                    
                    m_renderBuffer._drawBlankPolygon( m_whiteTexture, poly, m_planeColor );
                }
                
                break;
                
            /* $if SPUKMK3ME_DEBUG$ */
            default:
                Logger.log( "Unknown type of entity: " + entity.getType() + '\n' );
            /* $endif$ */
                
        }
    }
    
    @Override
    public ImageRegion createImageRegion( Image img, int x, int y, int w, int h )
    {
        return new LibgdxImageRegion( img, x, y, w, h );
    }
    
    @Override
    public Sprite createSprite( Image image, int x, int y, int w, int h,
        int spriteW, int spriteH, int[] framePositions, int[] frameSteps )
    {
        return new LibgdxSprite( image, x, y, w, h, spriteW, spriteH, framePositions, frameSteps );
    }
    
    void resize( int width, int height )
    {
        m_cam.direction.set( 0f, 0f, -1f );
        m_cam.up.set( 0f, 1f, 0f );
        m_cam.viewportWidth = width;
        m_cam.viewportHeight = height;
        m_cam.position.x = (float)width / 2;
        m_cam.position.y = (float)height / 2;
        m_cam.update();
    }
    
    void clearTextures()
    {
        final String IMGTYPE = "image";
        ResourceManager rMan = Platform.PLATFORM.getResourceManager();
        
        List<Resource> resList = rMan.getResources( IMGTYPE );
        
        for ( Resource res : resList )
        {
            if ( res.getType().equals( IMGTYPE ) )
                rMan.releaseResource( res );
        }
    }
    
    void markGLCreateNewContext()
    {
        m_glCreateNewContext = true;
    }
    
    private void reloadTextures()
    {
        final String IMGTYPE = "image";
        ResourceManager rMan = Platform.PLATFORM.getResourceManager();
        
        List<Resource> resList = rMan.getResources( IMGTYPE );
        
        try
        {
            for ( Resource res : resList )
            {
                if ( res.getType().equals( IMGTYPE ) )
                {   
                    rMan.loadResource( res );
                    System.out.println( "Reloaded " + res.getFileHandle().getPath() );
                }
            }
        } catch ( IOException e ) {
            e.printStackTrace();
        }
    }
    
    private static final String LIBGDX_VIDEOAPI_NAME    = "libgdx";
    private static final String LIBGDX_VIDEOAPI_VER     = "0.1";
    
    // Drawing data
    private OrthographicCamera  m_cam;
    private RenderBuffer        m_renderBuffer;
    
    private Texture             m_whiteTexture;
    private float[]             m_additionalColor;
    private float               m_planeColor;
    private boolean             m_useAdditionalColor, m_useBlending;
    
    private Matrix4f[]          m_mvMatrixStack;
    private Vector3f[]          m_mvScaleStack, m_mvTransStack;
    private int[]               m_mvNearestGLMatrix;
    private boolean[]           m_mvNeedUpdateGLMatrix;
    private int                 m_mvMatrixStackIndex;
    private int                 m_mvMatrixGLStackSize;
    
    private Rectangle           m_clipData;
    
    private long                m_deltaTimeL;
    private int                 m_deltaTimeI;
    
    private boolean m_initialized, m_glCreateNewContext;    
}
